using System;

namespace ProfanityChecker
{
  public sealed class PC1Encryption
  {
    private PC1Encryption() {}

    private const string C_Generic_Enc_Key = "ScottIsAFool";

    private static ushort ax = 0;
    private static ushort bx = 0;
    private static ushort cx = 0;
    private static ushort dx = 0;
    private static ushort si = 0;
    private static ushort tmp = 0;
    private static ushort x1a2 = 0;
    private static ushort res = 0;
    private static ushort i = 0;
    private static ushort inter = 0;
    private static ushort cfc = 0;
    private static ushort cfd = 0;
    private static ushort l = 0;
    private static ushort[] x1a0 = new ushort[8] {0,0,0,0,0,0,0,0};
    private static char[] cle = new char[16];
    private static char[] cry = new char[33001];

    private static void Code() 
    {
      dx = (ushort)(x1a2 + i);
      ax = x1a0[i];
      cx = 0x015a;
      bx = 0x4e35;
      tmp = ax;
      ax = si;
      si = tmp;
      tmp = ax;
      ax = dx;
      dx = tmp; 
      if (ax != 0) ax *= bx;
      tmp = ax;
      ax = cx;
      cx = tmp;
      if (ax != 0) 
      {
        ax *= si;
        cx += ax;
      }
      tmp = ax;
      ax = si;
      si = tmp;
      ax *= bx;
      dx += cx;
      ++ax;
      x1a2 = dx;
      x1a0[i] = ax;
      res = (ushort)(ax ^ dx);
      ++i;
    }

    private static void Assemble() 
    {
      x1a0[0] = (ushort)((cle[0] * 256) + cle[1]); 
      Code(); 
      inter = res;
      x1a0[1] = (ushort)(x1a0[0] ^ ( ((ushort)cle[2]*256) + (ushort)cle[3] ));
      Code(); 
      inter = (ushort)(inter ^ res);
      x1a0[2] = (ushort)(x1a0[1] ^ ( ((ushort)cle[4]*256) + (ushort)cle[5] ));
      Code(); 
      inter = (ushort)(inter ^ res);
      x1a0[3] = (ushort)(x1a0[2] ^ ( ((ushort)cle[6]*256) + (ushort)cle[7] ));
      Code(); 
      inter = (ushort)(inter ^ res);
      x1a0[4] = (ushort)(x1a0[3] ^ ( ((ushort)cle[8]*256) + (ushort)cle[9] ));
      Code(); 
      inter = (ushort)(inter ^ res);
      x1a0[5] = (ushort)(x1a0[4] ^ ( ((ushort)cle[10]*256) + (ushort)cle[11] ));
      Code(); 
      inter = (ushort)(inter ^ res);
      x1a0[6] = (ushort)(x1a0[5] ^ ( ((ushort)cle[12]*256) + (ushort)cle[13] ));
      Code(); 
      inter = (ushort)(inter ^ res);
      x1a0[7] = (ushort)(x1a0[6] ^ ( ((ushort)cle[14]*256) + (ushort)cle[15] ));
      Code(); 
      inter = (ushort)(inter ^ res);
      i = 0;
    }

    private static void Encrypt (char[] key, char[] buffer, int bufferLength) 
    {      
      char rep = '\0';
      byte c = 0, d = 0, e = 0;
      ushort compte = 0, j = 0;

      Array.Clear(cry, 0, cry.GetLength(0));
      Array.Clear(cle, 0, cle.GetLength(0));
      Array.Copy(key, 0, cle, 0, key.Length);
      si = x1a2 = i = 0;
      
      for (j=0; j<bufferLength; j++) 
      {
        c = (byte)buffer[j];
        Assemble();
        cfc = (ushort)(inter >> 8);
        cfd = (ushort)(inter & 255);
        for (compte=0; compte<=15; compte++) 
        {
          cle[compte] = (char)(cle[compte] ^ c);
        }
      
        c ^= (byte)(cfc ^ cfd);
        d = (byte)(c >> 4); 
        e = (byte)(c & 15);
        switch (d) 
        {
          case 0: rep = 'a'; break;
          case 1: rep = 'b'; break;
          case 2: rep = 'c'; break;
          case 3: rep = 'd'; break;
          case 4: rep = 'e'; break;
          case 5: rep = 'f'; break;
          case 6: rep = 'g'; break;
          case 7: rep = 'h'; break;
          case 8: rep = 'i'; break;
          case 9: rep = 'j'; break;
          case 10: rep = 'k'; break;
          case 11: rep = 'l'; break;
          case 12: rep = 'm'; break;
          case 13: rep = 'n'; break;
          case 14: rep = 'o'; break;
          case 15: rep = 'p'; break;
        }
        cry[j*2] = rep;

        switch (e) 
        {
          case 0: rep = 'a'; break;
          case 1: rep = 'b'; break;
          case 2: rep = 'c'; break;
          case 3: rep = 'd'; break;
          case 4: rep = 'e'; break;
          case 5: rep = 'f'; break;
          case 6: rep = 'g'; break;
          case 7: rep = 'h'; break;
          case 8: rep = 'i'; break;
          case 9: rep = 'j'; break;
          case 10: rep = 'k'; break;
          case 11: rep = 'l'; break;
          case 12: rep = 'm'; break;
          case 13: rep = 'n'; break;
          case 14: rep = 'o'; break;
          case 15: rep = 'p'; break;
        }
        cry[j*2+1] = rep;
      }
    }

    private static void Decrypt (char[] key, char[] buffer, int bufferLength) 
    {
      char rep = '\0';
      byte c = 0, d = 0, e = 0;
      ushort compte = 0, j = 0;
       
      Array.Clear(cry, 0, cry.GetLength(0));
      Array.Clear(cle, 0, cle.GetLength(0));
      Array.Copy(key, 0, cle, 0, key.Length);
      si = x1a2 = i = j = l = 0;

      while (j < bufferLength - 1) 
      {
        rep = buffer[j];

        switch (rep) 
        {
          case 'a': d = 0; break;
          case 'b': d = 1; break;
          case 'c': d = 2; break;
          case 'd': d = 3; break;
          case 'e': d = 4; break;
          case 'f': d = 5; break;
          case 'g': d = 6; break;
          case 'h': d = 7; break;
          case 'i': d = 8; break;
          case 'j': d = 9; break;
          case 'k': d = 10; break;
          case 'l': d = 11; break;
          case 'm': d = 12; break;
          case 'n': d = 13; break;
          case 'o': d = 14; break;
          case 'p': d = 15; break;
        }
        d = (byte)(d << 4);
        ++j;

        rep = buffer[j];
        switch (rep) 
        {
          case 'a': e = 0; break;
          case 'b': e = 1; break;
          case 'c': e = 2; break;
          case 'd': e = 3; break;
          case 'e': e = 4; break;
          case 'f': e = 5; break;
          case 'g': e = 6; break;
          case 'h': e = 7; break;
          case 'i': e = 8; break;
          case 'j': e = 9; break;
          case 'k': e = 10; break;
          case 'l': e = 11; break;
          case 'm': e = 12; break;
          case 'n': e = 13; break;
          case 'o': e = 14; break;
          case 'p': e = 15; break;
        }
        c = (byte)(d + e);
        Assemble();
        cfc = (ushort)(inter >> 8);
        cfd = (ushort)(inter & 255);
        c ^= (byte)(cfc ^ cfd);

        for (compte=0; compte <= 15; compte++) 
        {
          cle[compte] ^= (char)c;
        }

        cry[l] = (char)c;
        j++;
        l++;
      }     
    }

    /* encryptString
     * 
     * Returns the encryption of the given string using the given key.
     * */
    public static string EncryptString(string theString, string key) 
    {
      if (key.Length == 0) 
      {
        key = C_Generic_Enc_Key;
      }

      Encrypt(key.ToCharArray(), theString.ToCharArray(), theString.Length);
      return new string(cry, 0, Array.IndexOf(cry, '\0'));
    }    

    /* encryptString
     * 
     * Returns the encryption of the given string using the default key.
     * */
    public static string EncryptString(string theString) 
    {
      return EncryptString(theString, C_Generic_Enc_Key);      
    }    

    /* decryptString
     * 
     * Returns the decryption of the given string using the given key.
     * */
    public static string DecryptString(string theString, string key) 
    {
      if (key.Length == 0) 
      {
        key = C_Generic_Enc_Key;
      }

      Decrypt(key.ToCharArray(), theString.ToCharArray(), theString.Length);
      return new string(cry, 0, Array.IndexOf(cry, '\0'));
    }    

    /* decryptString
     * 
     * Returns the decryption of the given string using the default key.
     * */
    public static string DecryptString(string theString) 
    {
      return DecryptString(theString, C_Generic_Enc_Key);
    }    
  }
}
